<!--
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL City</title>
<style>
html, body {
  width: 100%;
  height: 100%;
  border: 0px;
  padding: 0px;
  margin: 0px;
  background-color: red;
  font-family: sans-serif;
  overflow: hidden;
  color: #fff;
}
a {
  color: #fff;
}
#info {
    font-size: small;
    position: absolute;
  	top: 0px; width: 100%;
  	padding: 5px;
  	text-align: center;
  	z-index: 2;
}
CANVAS {
  background-color: gray;
}
#canvas2d {
  display: none;
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 3;
}
.fpsContainer {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 3;
  color: white;
  font-family: sans-serif;
  background-color: rgba(0,0,0,0.5);
  border-radius: 10px;
  padding: 10px;
}
.fpsContainer a:visited, .fpsContainer a:hover, .fpsContainer a {
  color: white;
  font-size: xx-small;
}
#viewContainer {
  width: 100%;
  height: 100%;
}
</style>
<script src="../tdl/base.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.particles');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');
window.onload = initialize;

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.
var g_fpsTimer;           // object to measure frames per second;
var g_logGLCalls = true;  // whether or not to log webgl calls
var g_debug = false;      // whether or not to debug.
var g_drawOnce = false;   // draw just one frame.
var g_setCountElements = [];
var ctx;                  // 2d context
var canvas2d;             // 2d canvas
var g_windowTextures = [];// ..
var g_blocksAcross = 10;
var g_lightsPerBlock = 10;
var g_buildingsAcrossBlock = 8;
var g_buildingWidth = 10;
var g_buildingSpacing = 11;
var g_roadWidth = 16;
var g_blockWidth = g_buildingSpacing * g_buildingsAcrossBlock + g_roadWidth;
var g_worldWidth = g_blockWidth * g_blocksAcross;
var g_lightOffset = -8; //g_buildingWidth * 0.5;

var B;
var IDX;

//g_drawOnce = true;
//g_debug = true;

var g_eyeSpeed          = 0.01;
var g_eyeHeight         = 120;
var g_eyeTarget         = -100;
var g_eyeRadius         = 600;
var g_widthBasis        = 25;
var g_heightBasis       = 25;

function ValidateNoneOfTheArgsAreUndefined(functionName, args) {
  for (var ii = 0; ii < args.length; ++ii) {
    if (args[ii] === undefined) {
      tdl.error("undefined passed to gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
    }
  }
}

function Log(msg) {
  if (g_logGLCalls) {
    tdl.log(msg);
  }
}

function LogGLCall(functionName, args) {
  if (g_logGLCalls) {
    ValidateNoneOfTheArgsAreUndefined(functionName, args)
    tdl.log("gl." + functionName + "(" +
            tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
  }
}

/**
 * Sets up Ground.
 */
function setupGround() {
  var textures = {};
  var program = tdl.programs.loadProgramFromScriptTags(
      'constVertexShader',
      'constFragmentShader');
  var arrays = tdl.primitives.createPlane(
      g_worldWidth, g_worldWidth, 10, 10);
  var offset = (g_buildingWidth + g_roadWidth) * -0.5
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, 1, 0,
       offset, 0, offset, 1]);

  delete arrays.normal;
  delete arrays.texCoord;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up Skybox.
 */
function setupSkybox() {
  var textures = {
    skybox: tdl.textures.loadTexture([
        'assets/space_rt.jpg',
        'assets/space_lf.jpg',
        'assets/space_up.jpg',
        'assets/space_dn.jpg',
        'assets/space_fr.jpg',
        'assets/space_bk.jpg'])
  };
  var program = tdl.programs.loadProgramFromScriptTags(
      'skyboxVertexShader',
      'skyboxFragmentShader');
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  delete arrays['normal'];
  delete arrays['texCoord'];
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, 1, 0,
       0,-1, 0, 0,
       0, 0, 0.99, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

var createBuildingCube = function(width, height, depth) {
  var cubeFaceIndices = [
    [4, 5, 7, 6],
    [5, 1, 3, 7],
    [1, 0, 2, 3],
    [0, 4, 6, 2],
    [6, 7, 3, 2],
    [1, 0, 4, 5],
  ];

  var halfWidth  = width  * 0.5;
  var halfHeight = height * 0.5;
  var halfDepth  = depth  * 0.5;

  var cornerVertices = [
    [-halfWidth, -halfHeight, -halfDepth], // 0 left  back  bottom
    [+halfWidth, -halfHeight, -halfDepth], // 1 right back  bottom
    [-halfWidth, +halfHeight, -halfDepth], // 2 left  back  top
    [+halfWidth, +halfHeight, -halfDepth], // 3 right back  top
    [-halfWidth, -halfHeight, +halfDepth], // 4 left  front bottom
    [+halfWidth, -halfHeight, +halfDepth], // 5 right front bottom
    [-halfWidth, +halfHeight, +halfDepth], // 6 left  front top
    [+halfWidth, +halfHeight, +halfDepth]  // 7 right front top
  ];

  var faceNormals = [
    [+1, +0, +0],
    [-1, +0, +0],
    [+0, +1, +0],
    [+0, -1, +0],
    [+0, +0, +1],
    [+0, +0, -1]
  ];

  var uvCoords = [
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1]
  ];

  var wf = width  / g_widthBasis;
  var hf = height / g_heightBasis;
  var faceUVMult = [
    [wf, hf],
    [wf, hf],
    [wf, hf],
    [wf, hf],
    [ 0,  0],
    [ 0,  0]
  ];

  var numVertices = 6 * 4;
  var positions = new tdl.primitives.AttribBuffer(3, numVertices);
  var normals = new tdl.primitives.AttribBuffer(3, numVertices);
  var texCoords = new tdl.primitives.AttribBuffer(2, numVertices);
  var indices = new tdl.primitives.AttribBuffer(3, 6 * 2, 'Uint16Array');

  for (var f = 0; f < 6; ++f) {
    var faceIndices = cubeFaceIndices[f];
    var uvMult = faceUVMult[f];
    for (var v = 0; v < 4; ++v) {
      var position = cornerVertices[faceIndices[v]];
      var normal = faceNormals[f];
      var uv = [uvMult[0] * uvCoords[v][0], uvMult[1] * uvCoords[v][1]];
      // Each face needs all four vertices because the normals and texture
      // coordinates are not all the same.
      positions.push(position);
      normals.push(normal);
      texCoords.push(uv);
    }

    // Two triangles make a square face.
    var offset = 4 * f;
    indices.push([offset + 0, offset + 1, offset + 2]);
    indices.push([offset + 0, offset + 2, offset + 3]);
  }

  return {
    position: positions,
    normal: normals,
    texCoord: texCoords,
    indices: indices};
};

function createBuildingPyramid(size) {
  var k = size / 2;

  var pyramidFaceIndices = [
    [0, 4, 1],
    [1, 4, 2],
    [2, 4, 3],
    [3, 4, 0]
  ];

  var cornerVertices = [
    [-k,  0, -k],
    [+k,  0, -k],
    [+k,  0, +k],
    [-k,  0, +k],
    [0,   k,  0],
  ];

  var uvCoords = [
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1]
  ];

  var numVertices = 5;
  var positions = new tdl.primitives.AttribBuffer(3, numVertices);
  var normals = new tdl.primitives.AttribBuffer(3, numVertices);
  var texCoords = new tdl.primitives.AttribBuffer(2, numVertices);
  var indices = new tdl.primitives.AttribBuffer(3, 6 * 2, 'Uint16Array');

  for (var v = 0; v < cornerVertices.length; ++v) {
    positions.push(cornerVertices[v]);
    normals.push([0, 1, 0]);  // don't care. Going to remove anyway
    texCoords.push([0, 0]);  // don't care. Going to remove anyway
  }

  for (var f = 0; f < pyramidFaceIndices.length; ++f) {
    var faceIndices = pyramidFaceIndices[f];
    indices.push(faceIndices);
  }

  return {
    position: positions,
    normal: normals,
    texCoord: texCoords,
    indices: indices};
}

/**
 * Sets up a modern building
 */
function setupModernBuilding(height) {
  var towers = [];
  var numTowers = 4;
  var maxHeight = height;
  for (var ii = 0; ii < numTowers; ++ii) {
    var start = ii == 0 ? 5 : 2;
    var range = ii == 0 ? 3 : 4;
    var height = ii == 0 ? maxHeight : math.randomInt(maxHeight);
    var width = start + math.randomInt(range);
    var depth = start + math.randomInt(range);
    width = 10;
    depth = 10;
    var x = Math.round((Math.random() - 0.5) * (1 - width));
    var z = Math.round((Math.random() - 0.5) * (1 - depth));
    var tower = createBuildingCube(width, height, depth);
    delete tower.normal;
    tdl.primitives.reorient(tower,
        [1, 0, 0, 0,
         0, 1, 0, 0,
         0, 0, 1, 0,
         -x * 0.5, height / 2, -z * 0.5, 1]);
    towers.push(tower);
  }
  return tdl.primitives.concat(towers);
}

/**
 * Sets up a classic building
 */
function setupClassicBuilding(height) {
  var towers = [];
  var numTowers = 2 + math.randomInt(3);
  var maxHeight = height;
  for (var ii = 0; ii < numTowers; ++ii) {
    var width = (1 - ii * 0.1) * 10;
    var height = (1 - (0.5 / (ii + 1))) * maxHeight;
    var tower = createBuildingCube(width, height, width);
    delete tower.normal;
    tdl.primitives.reorient(
        tower,
        [1, 0, 0, 0,
         0, 1, 0, 0,
         0, 0, 1, 0,
         0, height / 2, 0, 1]);
    towers.push(tower);
  }
  var width = (1 - numTowers * 0.1) * 10;
  var height = 10;
  var bottom = (1 - (0.5 / (numTowers))) * maxHeight;
  var pyramid = createBuildingPyramid(1);
  delete pyramid.normal;
  tdl.primitives.reorient(
      pyramid,
      math.matrix4.mul(
        math.matrix4.scaling([width, height, width]),
        math.matrix4.translation([0, bottom, 0])));
  towers.push(pyramid);
  return tdl.primitives.concat(towers);
}

/**
 * Creates vertices for a truncated cone, which is like a cylinder
 * except that it has different top and bottom radii. A truncated cone
 * can also be used to create cylinders and regular cones. The
 * truncated cone will be created centered about the origin, with the
 * y axis as its vertical axis. The created cone has position, normal
 * and uv streams.
 *
 * @param {number} bottomRadius Bottom radius of truncated cone.
 * @param {number} topRadius Top radius of truncated cone.
 * @param {number} height Height of truncated cone.
 * @param {number} radialSubdivisions The number of subdivisions around the
 *     truncated cone.
 * @param {number} verticalSubdivisions The number of subdivisions down the
 *     truncated cone.
 * @param {boolean} opt_topCap Create top cap. Default = true.
 * @param {boolean} opt_bottomCap Create bottom cap. Default =
 *        true.
 * @return {!Object.<string, !tdl.primitives.AttribBuffer>} The
 *         created plane vertices.
 */
function createTruncatedConeWithSkips(
    bottomRadius,
    topRadius,
    height,
    radialSubdivisions,
    verticalSubdivisions,
    maxNumSkips,
    maxNonSkipUnits,
    maxSkipUnits,
    opt_topCap,
    opt_bottomCap) {
  if (radialSubdivisions < 3) {
    throw Error('radialSubdivisions must be 3 or greater');
  }

  if (verticalSubdivisions < 1) {
    throw Error('verticalSubdivisions must be 1 or greater');
  }

  var topCap = (opt_topCap === undefined) ? true : opt_topCap;
  var bottomCap = (opt_bottomCap === undefined) ? true : opt_bottomCap;

  var extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0);

  var numSkips = math.randomInt(maxNumSkips);
  var arcPoints = [];
  var arcIndex = 0;
  var originalRadialSubdivisions = radialSubdivisions;
  for (var ii = 0; ii < numSkips; ++ii) {
    arcPoints.push(++arcIndex);
    var skipStart = math.randomInt(maxNonSkipUnits);
    for (var jj = 0; jj < skipStart; ++jj) {
      arcPoints.push(++arcIndex);
    }
    skipAmount = 1 + math.randomInt(maxSkipUnits - 1);
    arcIndex += skipAmount;
    radialSubdivisions -= skipAmount;
  }
  while (arcPoints.length <= radialSubdivisions) {
    arcPoints.push(++arcIndex);
  }

  var numVertices = (radialSubdivisions + 1) * (verticalSubdivisions + 1 + extra);
  var positions = new tdl.primitives.AttribBuffer(3, numVertices);
  var normals = new tdl.primitives.AttribBuffer(3, numVertices);
  var texCoords = new tdl.primitives.AttribBuffer(2, numVertices);
  var indices = new tdl.primitives.AttribBuffer(
      3, radialSubdivisions * (verticalSubdivisions + extra) * 2, 'Uint16Array');

  var vertsAroundEdge = radialSubdivisions + 1;

  // The slant of the cone is constant across its surface
  var slant = Math.atan2(bottomRadius - topRadius, height);
  var cosSlant = Math.cos(slant);
  var sinSlant = Math.sin(slant);

  var start = topCap ? -2 : 0;
  var end = verticalSubdivisions + (bottomCap ? 2 : 0);

  for (var yy = start; yy <= end; ++yy) {
    var v = yy / verticalSubdivisions
    var y = height * v;
    var ringRadius;
    if (yy < 0) {
      y = 0;
      v = 1;
      ringRadius = bottomRadius;
    } else if (yy >= verticalSubdivisions) {
      y = height;
      v = 1;
      ringRadius = topRadius;
    } else {
      ringRadius = bottomRadius +
        (topRadius - bottomRadius) * (yy / verticalSubdivisions);
    }
    if (yy == -2 || yy == verticalSubdivisions + 2) {
      ringRadius = 0;
      v = 0;
    }
    y -= height / 2;
    for (var ii = 0; ii < vertsAroundEdge; ++ii) {
      var arcIndex = arcPoints[ii];
      var u = arcIndex / originalRadialSubdivisions
      var angle = u * Math.PI * 2;
      var sin = Math.sin(angle);
      var cos = Math.cos(angle);
      positions.push([sin * ringRadius, y, cos * ringRadius]);
      normals.push([
          (yy < 0 || yy > verticalSubdivisions) ? 0 : (sin * cosSlant),
          (yy < 0) ? -1 : (yy > verticalSubdivisions ? 1 : sinSlant),
          (yy < 0 || yy > verticalSubdivisions) ? 0 : (cos * cosSlant)]);
      texCoords.push([u, v]);
    }
  }

  for (var yy = 0; yy < verticalSubdivisions + extra; ++yy) {
    for (var ii = 0; ii < radialSubdivisions; ++ii) {
      indices.push([vertsAroundEdge * (yy + 0) + 0 + ii,
                   vertsAroundEdge * (yy + 0) + 1 + ii,
                   vertsAroundEdge * (yy + 1) + 1 + ii]);
      indices.push([vertsAroundEdge * (yy + 0) + 0 + ii,
                   vertsAroundEdge * (yy + 1) + 1 + ii,
                   vertsAroundEdge * (yy + 1) + 0 + ii]);
    }
  }

  return {
    position: positions,
    normal: normals,
    texCoord: texCoords,
    indices: indices};
};

/**
 * Sets up a cylinder building
 */
function setupCylinderBuilding(height) {
  var arrays = createTruncatedConeWithSkips(
    5,     // bottomRadius,
    5,     // topRadius,
    height,// height,
    36,    // radialSubdivisions,
    1,     // verticalSubdivisions,
    4,     // maxNumSkips,
    9,     // maxNonSkipUnits,
    9);    // maxSkipUnits,
  delete arrays.normal;
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, 1, 0,
       0, 50, 0, 1]);
  return arrays;
}

function createRandomBuilding(height) {
  switch (math.randomInt(3)) {
  case 0:
    return setupModernBuilding(height);
  case 1:
    return setupCylinderBuilding(height);
  default:
    return setupClassicBuilding(height);
  }
}

function createBlock(windowTexture) {
  var textures = {
      diffuseSampler: windowTexture};
  var program = tdl.programs.loadProgramFromScriptTags(
      'buildingVertexShader',
      'buildingFragmentShader');
  var buildings = [];
  for (var xx = 0; xx < g_buildingsAcrossBlock; ++xx) {
    var arrays = createRandomBuilding(math.randomInt(70) + 30);
    tdl.primitives.reorient(
        arrays, math.matrix4.translation([xx * g_buildingSpacing, 0, 0]));
    buildings.push(arrays);
    var arrays = createRandomBuilding(math.randomInt(70) + 30);
    tdl.primitives.reorient(
        arrays, math.matrix4.translation(
          [xx * g_buildingSpacing,
           0,
           (g_buildingsAcrossBlock - 1) * g_buildingSpacing]));
    buildings.push(arrays);
    if (xx != 0 && xx != g_buildingsAcrossBlock - 1) {
      var arrays = createRandomBuilding(math.randomInt(70) + 30);
      tdl.primitives.reorient(
          arrays, math.matrix4.translation([0, 0, xx * g_buildingSpacing]));
      buildings.push(arrays);
      var arrays = createRandomBuilding(math.randomInt(70) + 30);
      tdl.primitives.reorient(
          arrays, math.matrix4.translation(
            [(g_buildingsAcrossBlock - 1) * g_buildingSpacing,
             0,
             xx * g_buildingSpacing]));
      buildings.push(arrays);
    }
  }
  var arrays = tdl.primitives.concat(buildings);
  return new tdl.models.Model(program, arrays, textures);
}

function createStreetLights() {
  var textures = {};
  var program = tdl.programs.loadProgramFromScriptTags(
      'streetLightVertexShader',
      'streetLightFragmentShader');
  var arrays = tdl.primitives.createPlane(1, 1, 2, 2);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, -1, 0,
       0, 1, 0, 0,
       0, 0, 0, 1]);
  delete arrays.normal;
  delete arrays.texCoord;
  var numElements = arrays.position.numElements;
  var lights = [];
  var across = g_lightsPerBlock;
  for (var xx = 0; xx < across; ++xx) {
    for (var ii = 0; ii < 4; ++ii) {
      var a2 = tdl.primitives.clone(arrays);
      lights.push(a2);
    }
  }
  var arrays = tdl.primitives.concat(lights);
  var numLights = across * 4;
  var lightPosition = new tdl.primitives.AttribBuffer(
      4, numElements * numLights);
  for (var xx = 0; xx < across; ++xx) {
    function addLight(x, y) {
      var r1 = Math.random();
      var r2 = Math.random();
      for (var jj = 0; jj < numElements; ++jj) {
        lightPosition.push([x, y, r1, r2]);
      }
    }
    addLight(xx + 1, 0);
    addLight(xx + 1, across + 1);
    addLight(0, xx + 1);
    addLight(across + 1, xx + 1);
  }
  arrays.lightPosition = lightPosition;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets the count
 */
function setCount(elem, count) {
  for (var ii = 0; ii < g_setCountElements.length; ++ii) {
    g_setCountElements[ii].style.color = "gray";
  }
  elem.style.color = "red";
}

/**
 * Sets up the count buttons.
 */
function setupCountButtons() {
  for (var ii = 0; ii < 100; ++ii) {
    var elem = document.getElementById("setCount" + ii);
    if (!elem) {
      break;
    }
    g_setCountElements.push(elem);
    elem.onclick = function(elem, count) {
      return function () {
        setCount(elem, count);
      }}(elem, ii);
  }
  setCount(document.getElementById('setCount0'), ii);
}

function createWindowTexture(ctx) {
  ctx.fillStyle = "rgb(0,0,0)";
  ctx.fillRect(0, 0, 512, 512);
  var windowHSpacing = 8;
  var windowVSpacing = 8;
  var windowsDown    = 512 / windowHSpacing;
  var windowsAcross  = 512 / windowVSpacing;
  var windowHBorder  = 2;
  var windowVBorder  = 2;
  var windowWidth    = windowHSpacing - windowHBorder;
  var windowHeight   = windowVSpacing - windowVBorder;
  for (var yy = 0; yy < windowsDown; ++yy) {
    for (var xx = 0; xx < windowsDown; ++xx) {
      var xOff = xx * windowHSpacing + Math.floor(windowHBorder * 0.5);
      var yOff = yy * windowVSpacing + Math.floor(windowVBorder * 0.5);
      ctx.fillStyle = "rgb(255,255,255)";
      ctx.fillRect(xOff, yOff, windowWidth, windowHeight);

      // Put something in bottom.
      if (Math.random() < 0.6) {
        ctx.fillStyle = "rgb(0,0,0)";
        var width = math.randomInt(windowWidth / 4) + windowWidth / 4 * 3;
        var x = math.randomInt(windowWidth - width);
        ctx.fillRect(xOff + x, yOff + windowHeight / 2, width, windowHeight / 2);
      }

      var bright = math.randomInt(10);

      // Put random colors in
      if (Math.random() < 0.6 || bright < 2) {
        var numPixels = math.randomInt(10) + 10;
        for (var ii = 0; ii < numPixels; ++ii) {
          ctx.fillStyle = "rgb(" +
              (math.randomInt(192) + 64) + "," +
              (math.randomInt(192) + 64) + "," +
              (math.randomInt(192) + 64) + ")";
          ctx.fillRect(
              xOff + math.randomInt(windowWidth),
              yOff + math.randomInt(windowHeight),
              1, 1);
        }
      }

      // darken
      if (bright < 2) {
        ctx.fillStyle = "rgba(0, 0, 0, 0.2)";
      } else if (bright < 4) {
        ctx.fillStyle = "rgba(0, 0, 0, 1)";
      } else if (bright < 8){
        ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
      } else {
        ctx.fillStyle = "rgba(0, 0, 0, 0.9)";
      }
      ctx.fillRect(xOff, yOff, windowWidth, windowHeight);
    }
  }
}

var g;
function initialize() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  g_fpsTimer = new tdl.fps.FPSTimer();

  setupCountButtons();

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }
  if (g_debug) {
    gl = tdl.webgl.makeDebugContext(gl, undefined, LogGLCall);
  }
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, gl.TRUE);

  canvas2d = document.getElementById("canvas2d");
  ctx = canvas2d.getContext("2d");

  createWindowTexture(ctx);

  g_windowTextures[0] = tdl.textures.loadTexture(canvas2d, true);
//  g_windowTextures[0] = tdl.textures.loadTexture('../tdl/test.png');

  blocks = [];

  Log("--Setup Ground---------------------------------------");
  var ground = setupGround();
  Log("--Setup Skybox---------------------------------------");
  var skybox = setupSkybox();
  Log("--Setup Block---------------------------------------");
  blocks.push(createBlock(g_windowTextures[0]));
  Log("--Setup StreetLights---------------------------------------");
  var streetLights = createStreetLights();

  var then = 0.0;
  var clock = 0.0;
  var fpsElem = document.getElementById("fps");

  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewProjectionInverse = new Float32Array(16);
  var viewDirectionProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // Sky uniforms.
  var skyConst = {
      viewDirectionProjectionInverse: viewDirectionProjectionInverse};
  var skyPer = {};

  // Ground uniforms.
  var groundConst = {};
  var groundPer = {
    worldViewProjection: worldViewProjection};

  // Modern building uniforms.
  var modernBuildingConst = {
    };
  var modernBuildingPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    texCoordMult: new Float32Array([1, 1])};
  // Cylinder building uniforms.
  var cylinderBuildingConst = {
    };
  var cylinderBuildingPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    texCoordMult: new Float32Array([1, 1])};
  // Classic building uniforms.
  var classicBuildingConst = {
    };
  var classicBuildingPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    texCoordMult: new Float32Array([1, 1])};

  var streetLightsConst = {
    viewInverse: viewInverse,
    lightSpacing: (g_buildingSpacing * g_buildingsAcrossBlock) / g_lightsPerBlock,
    lightSize: 4,
    };
  var streetLightsPer = {
    world: world,
    worldViewProjection: worldViewProjection};

  var pseudoRandom = math.pseudoRandom;
  var numBlocks = blocks.length;
  var blockInfo = [];
  math.resetPseudoRandom();
  for (var blockZ = 0; blockZ < g_blocksAcross; ++blockZ) {
    var row = [];
    blockInfo[blockZ] = row;
    var bz = (blockZ - g_blocksAcross * 0.5) * g_blockWidth;
    for (var blockX = 0; blockX < g_blocksAcross; ++blockX) {
      var bx = (blockX - g_blocksAcross * 0.5) * g_blockWidth;
      var index = Math.floor(pseudoRandom() * numBlocks);
      var model = blocks[index];
      var w = new Float32Array(16);
      var lw = new Float32Array(16);
      fast.matrix4.translation(w, [bx, 0, bz]),
      fast.matrix4.translation(
          lw,
          [bx + g_buildingWidth * 0.5,
           1,
           bz + g_buildingWidth * 0.5]),
      row[blockX] = {
        model: model,
        world: w,
        lightWorld: lw
      };
    }
  }

  var frameCount = 0;
  var eyeClock = 0;

  function render() {
    ++frameCount;
    if (!g_drawOnce) {
      requestAnimationFrame(render);
    }
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime;
    if(then == 0.0) {
      elapsedTime = 0.0;
    } else {
      elapsedTime = now - then;
    }
    then = now;

    g_fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = g_fpsTimer.averageFPS;

    clock += elapsedTime;
    eyeClock += elapsedTime * g_eyeSpeed;
    eyePosition[0] = Math.sin(eyeClock) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(eyeClock) * g_eyeRadius;
    target[1] = g_eyeTarget;

    for (var blockZ = 0; blockZ < g_blocksAcross; ++blockZ) {
      var row = blockInfo[blockZ];
      var bz = (blockZ - g_blocksAcross * 0.5) * g_blockWidth;
      for (var blockX = 0; blockX < g_blocksAcross; ++blockX) {
        var info = row[blockX];
        var bx = (blockX - g_blocksAcross * 0.5) * g_blockWidth;
        var index = Math.floor(pseudoRandom() * numBlocks);
        var model = blocks[index];
        fast.matrix4.translation(
            info.lightWorld,
            [bx + g_lightOffset,
             1,
             bz + g_lightOffset]);
      }
    }

    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(0,0,0,0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.disable(gl.BLEND);
    gl.depthFunc(gl.LEQUAL);
    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    fast.matrix4.perspective(
        projection,
        math.degToRad(60),
        canvas.clientWidth / canvas.clientHeight,
        1,
        500000);
    fast.matrix4.lookAt(
        view,
        eyePosition,
        target,
        up);
    fast.matrix4.mul(viewProjection, view, projection);
    fast.matrix4.inverse(viewInverse, view);
    fast.matrix4.inverse(viewProjectionInverse, viewProjection);
    fast.matrix4.copy(m4t0, view);
    fast.matrix4.setTranslation(m4t0, [0, 0, 0]);
    fast.matrix4.mul(m4t1, m4t0, projection);
    fast.matrix4.inverse(viewDirectionProjectionInverse, m4t1);

    fast.matrix4.getAxis(v3t0, viewInverse, 0); // x
    fast.matrix4.getAxis(v3t1, viewInverse, 1); // y;
    fast.matrix4.getAxis(v3t2, viewInverse, 2); // z;
    fast.mulScalarVector(v3t0, 10, v3t0);
    fast.mulScalarVector(v3t1, 10, v3t1);
    fast.mulScalarVector(v3t2, 10, v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);

//      view: view,
//      projection: projection,
//      viewProjection: viewProjection,

    Log("--Draw Skybox---------------------------------------");
    gl.depthMask(false);
    skybox.drawPrep(skyConst);
    skybox.draw(skyPer);
    gl.depthMask(true);

    Log("--Draw Ground---------------------------------------");
    ground.drawPrep(groundConst);
    fast.matrix4.translation(world, [0, 0, 0]),
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    ground.draw(groundPer);

    Log("--Draw Blocks---------------------------------------");
    var oldModel = null;
    for (var blockZ = 0; blockZ < g_blocksAcross; ++blockZ) {
      var row = blockInfo[blockZ];
      for (var blockX = 0; blockX < g_blocksAcross; ++blockX) {
        var info = row[blockX];
        var model = info.model;
        if (model != oldModel) {
          oldModel = model;
          model.drawPrep(modernBuildingConst);
        }
        fast.matrix4.copy(world, info.world);
        fast.matrix4.mul(worldViewProjection, world, viewProjection);
        fast.matrix4.inverse(worldInverse, world);
        model.draw(modernBuildingPer);
      }
    }

    Log("--Draw Street Lights---------------------------------------");
    gl.disable(gl.CULL_FACE);
    gl.enable(gl.BLEND);
    gl.depthMask(false);
    streetLights.drawPrep(streetLightsConst);
    for (var blockZ = 0; blockZ < g_blocksAcross; ++blockZ) {
      var row = blockInfo[blockZ];
      for (var blockX = 0; blockX < g_blocksAcross; ++blockX) {
        var info = row[blockX];
        fast.matrix4.copy(world, info.lightWorld);
        fast.matrix4.mul(worldViewProjection, world, viewProjection);
        streetLights.draw(streetLightsPer);
      }
    }

//    Log("--Draw Modern Building---------------------------------------");
//    modernBuilding.drawPrep(modernBuildingConst);
//    fast.matrix4.translation(world, [0, 0, 0]),
//    fast.matrix4.mul(worldViewProjection, world, viewProjection);
//    fast.matrix4.inverse(worldInverse, world);
//    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
//    modernBuilding.draw(modernBuildingPer);
//
//    Log("--Draw Cylinder Building---------------------------------------");
//    cylinderBuilding.drawPrep(cylinderBuildingConst);
//    fast.matrix4.translation(world, [11, 0, 0]),
//    fast.matrix4.mul(worldViewProjection, world, viewProjection);
//    fast.matrix4.inverse(worldInverse, world);
//    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
//    cylinderBuilding.draw(cylinderBuildingPer);
//
//    Log("--Draw Classic Building---------------------------------------");
//    classicBuilding.drawPrep(classicBuildingConst);
//    fast.matrix4.translation(world, [-11, 0, 0]),
//    fast.matrix4.mul(worldViewProjection, world, viewProjection);
//    fast.matrix4.inverse(worldInverse, world);
//    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
//    classicBuilding.draw(classicBuildingPer);


    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // turn off logging after 1 frame.
    g_logGLCalls = false;
  }
  render();
  return true;
}
</script>
</head>
<body>
<div id="info"><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - city</div>
<div class="fpsContainer">
  <div class="fps">fps: <span id="fps"></div>
  <div>Buildings</div>
  <div id="setCount0">1</div>
  <div id="setCount1">a few</div>
  <div id="setCount2">many</div>
  <div id="setCount3">lots</div>
  <div><a href="http://www.shamusyoung.com/twentysidedtale/?p=2940">Inspired By<br/>PixelCity</a></div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
<canvas id="canvas2d" width="512" height="512"></canvas>
</body>
<script id="constVertexShader" type="text/something-not-javascript">
attribute vec4 position;
uniform mat4 worldViewProjection;
void main() {
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="constFragmentShader" type="text/something-not-javascript">
precision mediump float;
void main() {
  gl_FragColor = vec4(0, 0, 0, 1);
}
</script>
<!--- DiffusePoint -->
<script id="diffusePointVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  gl_Position = v_position;
}

</script>
<script id="diffusePointFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform sampler2D diffuseSampler;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  vec3 normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), 1.0);
  gl_FragColor = vec4((diffuse * litR.y).rgb, diffuse.a);
}
</script>
<!-- ===[ Building ]============================================== -->
<script id="buildingVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform mat4 world;
uniform vec2 texCoordMult;
attribute vec4 position;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
void main() {
  v_texCoord = texCoord * texCoordMult;
  v_position = (worldViewProjection * position);
  gl_Position = v_position;
}

</script>
<script id="buildingFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;

uniform sampler2D diffuseSampler;

void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  gl_FragColor = diffuse;
}
</script>
<!-- ===[ Street Light ]============================================== -->
<script id="streetLightVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform mat4 world;
uniform mat4 viewInverse;
uniform float lightSpacing;
uniform float lightSize;
attribute vec4 position;
attribute vec4 lightPosition;
varying vec4 v_color;

void main() {
  vec3 basisX = viewInverse[0].xyz;
  vec3 basisZ = viewInverse[1].xyz;
  vec3 subLocalPosition = vec3(
      basisX * position.x + basisZ * position.y) * lightSize;
  float xbase = lightPosition.x;
  float zbase = lightPosition.y;
  vec4 localPosition = vec4(
      subLocalPosition.x + xbase * lightSpacing,
      subLocalPosition.y                       ,
      subLocalPosition.z + zbase * lightSpacing,
      1);

  gl_Position = worldViewProjection * localPosition;
  float d = length(position.xy * 2.0);
  float intensity = 1.0 - d;
  v_color = vec4(1, 1, 1, intensity);
}

</script>
<script id="streetLightFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}
</script>
<!-- ===[ SkyBox ]============================================== -->
<script id="skyboxVertexShader" type="text/something-not-javascript">
attribute vec4 position;
varying vec4 v_position;
void main() {
  v_position = position;
  gl_Position = position;
}
</script>
<script id="skyboxFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform samplerCube skybox;
uniform mat4 viewDirectionProjectionInverse;
varying vec4 v_position;
void main() {
  vec4 t = (viewDirectionProjectionInverse * v_position);
  vec4 color = textureCube(
      skybox,
      normalize(t.xyz / t.w));
  gl_FragColor = color * color * color;
}
</script>
</html>


